<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>WaterFull</title>

  <!-- <script src="https://unpkg.com/petite-vue"></script> -->
  <script>
    // petite-vue
    var pn = Object.defineProperty, hn = (e, t, n) => t in e ? pn(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n, C = (e, t, n) => (hn(e, "symbol" != typeof t ? t + "" : t, n), n), PetiteVue = function (e) { "use strict"; function t(e) { if (a(e)) { const n = {}; for (let s = 0; s < e.length; s++) { const i = e[s], o = d(i) ? r(i) : t(i); if (o) for (const e in o) n[e] = o[e] } return n } return d(e) || g(e) ? e : void 0 } const n = /;(?![^(]*\))/g, s = /:(.+)/; function r(e) { const t = {}; return e.split(n).forEach((e => { if (e) { const n = e.split(s); n.length > 1 && (t[n[0].trim()] = n[1].trim()) } })), t } function i(e) { let t = ""; if (d(e)) t = e; else if (a(e)) for (let n = 0; n < e.length; n++) { const s = i(e[n]); s && (t += s + " ") } else if (g(e)) for (const n in e) e[n] && (t += n + " "); return t.trim() } function o(e, t) { if (e === t) return !0; let n = h(e), s = h(t); if (n || s) return !(!n || !s) && e.getTime() === t.getTime(); if (n = a(e), s = a(t), n || s) return !(!n || !s) && function (e, t) { if (e.length !== t.length) return !1; let n = !0; for (let s = 0; n && s < e.length; s++)n = o(e[s], t[s]); return n }(e, t); if (n = g(e), s = g(t), n || s) { if (!n || !s) return !1; if (Object.keys(e).length !== Object.keys(t).length) return !1; for (const n in e) { const s = e.hasOwnProperty(n), r = t.hasOwnProperty(n); if (s && !r || !s && r || !o(e[n], t[n])) return !1 } } return String(e) === String(t) } function c(e, t) { return e.findIndex((e => o(e, t))) } const l = Object.assign, f = Object.prototype.hasOwnProperty, u = (e, t) => f.call(e, t), a = Array.isArray, p = e => "[object Map]" === y(e), h = e => e instanceof Date, d = e => "string" == typeof e, m = e => "symbol" == typeof e, g = e => null !== e && "object" == typeof e, v = Object.prototype.toString, y = e => v.call(e), b = e => d(e) && "NaN" !== e && "-" !== e[0] && "" + parseInt(e, 10) === e, x = e => { const t = Object.create(null); return n => t[n] || (t[n] = e(n)) }, _ = /-(\w)/g, w = x((e => e.replace(_, ((e, t) => t ? t.toUpperCase() : "")))), $ = /\B([A-Z])/g, k = x((e => e.replace($, "-$1").toLowerCase())), O = e => { const t = parseFloat(e); return isNaN(t) ? e : t }; function S(e, t) { (t = t || undefined) && t.active && t.effects.push(e) } const E = e => { const t = new Set(e); return t.w = 0, t.n = 0, t }, j = e => (e.w & N) > 0, A = e => (e.n & N) > 0, P = new WeakMap; let R = 0, N = 1; const T = []; let M; const B = Symbol(""), L = Symbol(""); class W { constructor(e, t = null, n) { this.fn = e, this.scheduler = t, this.active = !0, this.deps = [], S(this, n) } run() { if (!this.active) return this.fn(); if (!T.includes(this)) try { return T.push(M = this), F.push(V), V = !0, N = 1 << ++R, R <= 30 ? (({ deps: e }) => { if (e.length) for (let t = 0; t < e.length; t++)e[t].w |= N })(this) : I(this), this.fn() } finally { R <= 30 && (e => { const { deps: t } = e; if (t.length) { let n = 0; for (let s = 0; s < t.length; s++) { const r = t[s]; j(r) && !A(r) ? r.delete(e) : t[n++] = r, r.w &= ~N, r.n &= ~N } t.length = n } })(this), N = 1 << --R, z(), T.pop(); const e = T.length; M = e > 0 ? T[e - 1] : void 0 } } stop() { this.active && (I(this), this.onStop && this.onStop(), this.active = !1) } } function I(e) { const { deps: t } = e; if (t.length) { for (let n = 0; n < t.length; n++)t[n].delete(e); t.length = 0 } } function K(e) { e.effect.stop() } let V = !0; const F = []; function z() { const e = F.pop(); V = void 0 === e || e } function H(e, t, n) { if (!V || void 0 === M) return; let s = P.get(e); s || P.set(e, s = new Map); let r = s.get(n); r || s.set(n, r = E()), function (e, t) { let n = !1; R <= 30 ? A(e) || (e.n |= N, n = !j(e)) : n = !e.has(M), n && (e.add(M), M.deps.push(e)) }(r) } function J(e, t, n, s, r, i) { const o = P.get(e); if (!o) return; let c = []; if ("clear" === t) c = [...o.values()]; else if ("length" === n && a(e)) o.forEach(((e, t) => { ("length" === t || t >= s) && c.push(e) })); else switch (void 0 !== n && c.push(o.get(n)), t) { case "add": a(e) ? b(n) && c.push(o.get("length")) : (c.push(o.get(B)), p(e) && c.push(o.get(L))); break; case "delete": a(e) || (c.push(o.get(B)), p(e) && c.push(o.get(L))); break; case "set": p(e) && c.push(o.get(B)) }if (1 === c.length) c[0] && Z(c[0]); else { const e = []; for (const t of c) t && e.push(...t); Z(E(e)) } } function Z(e, t) { for (const n of a(e) ? e : [...e]) (n !== M || n.allowRecurse) && (n.scheduler ? n.scheduler() : n.run()) } const q = function (e, t) { const n = Object.create(null), s = e.split(","); for (let r = 0; r < s.length; r++)n[s[r]] = !0; return t ? e => !!n[e.toLowerCase()] : e => !!n[e] }("__proto__,__v_isRef,__isVue"), D = new Set(Object.getOwnPropertyNames(Symbol).map((e => Symbol[e])).filter(m)), G = X(), U = X(!0), Q = function () { const e = {}; return ["includes", "indexOf", "lastIndexOf"].forEach((t => { e[t] = function (...e) { const n = le(this); for (let t = 0, r = this.length; t < r; t++)H(n, 0, t + ""); const s = n[t](...e); return -1 === s || !1 === s ? n[t](...e.map(le)) : s } })), ["push", "pop", "shift", "unshift", "splice"].forEach((t => { e[t] = function (...e) { F.push(V), V = !1; const n = le(this)[t].apply(this, e); return z(), n } })), e }(); function X(e = !1, t = !1) { return function (n, s, r) { if ("__v_isReactive" === s) return !e; if ("__v_isReadonly" === s) return e; if ("__v_raw" === s && r === (e ? t ? re : se : t ? ne : te).get(n)) return n; const i = a(n); if (!e && i && u(Q, s)) return Reflect.get(Q, s, r); const o = Reflect.get(n, s, r); return (m(s) ? D.has(s) : q(s)) || (e || H(n, 0, s), t) ? o : fe(o) ? i && b(s) ? o : o.value : g(o) ? e ? function (e) { return ce(e, !0, ee, null, se) }(o) : oe(o) : o } } const Y = { get: G, set: function (e = !1) { return function (t, n, s, r) { let i = t[n]; if (!e && !function (e) { return !(!e || !e.__v_isReadonly) }(s) && (s = le(s), i = le(i), !a(t) && fe(i) && !fe(s))) return i.value = s, !0; const o = a(t) && b(n) ? Number(n) < t.length : u(t, n), c = Reflect.set(t, n, s, r); return t === le(r) && (o ? ((e, t) => !Object.is(e, t))(s, i) && J(t, "set", n, s) : J(t, "add", n, s)), c } }(), deleteProperty: function (e, t) { const n = u(e, t); e[t]; const s = Reflect.deleteProperty(e, t); return s && n && J(e, "delete", t, void 0), s }, has: function (e, t) { const n = Reflect.has(e, t); return (!m(t) || !D.has(t)) && H(e, 0, t), n }, ownKeys: function (e) { return H(e, 0, a(e) ? "length" : B), Reflect.ownKeys(e) } }, ee = { get: U, set: (e, t) => !0, deleteProperty: (e, t) => !0 }, te = new WeakMap, ne = new WeakMap, se = new WeakMap, re = new WeakMap; function ie(e) { return e.__v_skip || !Object.isExtensible(e) ? 0 : function (e) { switch (e) { case "Object": case "Array": return 1; case "Map": case "Set": case "WeakMap": case "WeakSet": return 2; default: return 0 } }((e => y(e).slice(8, -1))(e)) } function oe(e) { return e && e.__v_isReadonly ? e : ce(e, !1, Y, null, te) } function ce(e, t, n, s, r) { if (!g(e) || e.__v_raw && (!t || !e.__v_isReactive)) return e; const i = r.get(e); if (i) return i; const o = ie(e); if (0 === o) return e; const c = new Proxy(e, 2 === o ? s : n); return r.set(e, c), c } function le(e) { const t = e && e.__v_raw; return t ? le(t) : e } function fe(e) { return Boolean(e && !0 === e.__v_isRef) } Promise.resolve(); let ue = !1; const ae = [], pe = Promise.resolve(), he = e => pe.then(e), de = e => { ae.includes(e) || ae.push(e), ue || (ue = !0, he(me)) }, me = () => { for (const e of ae) e(); ae.length = 0, ue = !1 }, ge = /^(spellcheck|draggable|form|list|type)$/, ve = ({ el: e, get: t, effect: n, arg: s, modifiers: r }) => { let i; "class" === s && (e._class = e.className), n((() => { let n = t(); if (s) (null == r ? void 0 : r.camel) && (s = w(s)), ye(e, s, n, i); else { for (const t in n) ye(e, t, n[t], i && i[t]); for (const t in i) (!n || !(t in n)) && ye(e, t, null) } i = n })) }, ye = (e, n, s, r) => { if ("class" === n) e.setAttribute("class", i(e._class ? [e._class, s] : s) || ""); else if ("style" === n) { s = t(s); const { style: n } = e; if (s) if (d(s)) s !== r && (n.cssText = s); else { for (const e in s) xe(n, e, s[e]); if (r && !d(r)) for (const e in r) null == s[e] && xe(n, e, "") } else e.removeAttribute("style") } else e instanceof SVGElement || !(n in e) || ge.test(n) ? "true-value" === n ? e._trueValue = s : "false-value" === n ? e._falseValue = s : null != s ? e.setAttribute(n, s) : e.removeAttribute(n) : (e[n] = s, "value" === n && (e._value = s)) }, be = /\s*!important$/, xe = (e, t, n) => { a(n) ? n.forEach((n => xe(e, t, n))) : t.startsWith("--") ? e.setProperty(t, n) : be.test(n) ? e.setProperty(k(t), n.replace(be, ""), "important") : e[t] = n }, _e = (e, t) => { const n = e.getAttribute(t); return null != n && e.removeAttribute(t), n }, we = (e, t, n, s) => { e.addEventListener(t, n, s) }, $e = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/, ke = ["ctrl", "shift", "alt", "meta"], Oe = { stop: e => e.stopPropagation(), prevent: e => e.preventDefault(), self: e => e.target !== e.currentTarget, ctrl: e => !e.ctrlKey, shift: e => !e.shiftKey, alt: e => !e.altKey, meta: e => !e.metaKey, left: e => "button" in e && 0 !== e.button, middle: e => "button" in e && 1 !== e.button, right: e => "button" in e && 2 !== e.button, exact: (e, t) => ke.some((n => e[`${n}Key`] && !t[n])) }, Se = ({ el: e, get: t, exp: n, arg: s, modifiers: r }) => { if (!s) return; let i = $e.test(n) ? t(`(e => ${n}(e))`) : t(`($event => { ${n} })`); if ("vue:mounted" !== s) { if ("vue:unmounted" === s) return () => i(); if (r) { "click" === s && (r.right && (s = "contextmenu"), r.middle && (s = "mouseup")); const e = i; i = t => { if (!("key" in t) || k(t.key) in r) { for (const e in r) { const n = Oe[e]; if (n && n(t, r)) return } return e(t) } } } we(e, s, i, r) } else he(i) }, Ee = ({ el: e, get: t, effect: n }) => { n((() => { e.textContent = Ce(t()) })) }, Ce = e => null == e ? "" : g(e) ? JSON.stringify(e, null, 2) : String(e), je = e => "_value" in e ? e._value : e.value, Ae = (e, t) => { const n = t ? "_trueValue" : "_falseValue"; return n in e ? e[n] : t }, Pe = e => { e.target.composing = !0 }, Re = e => { const t = e.target; t.composing && (t.composing = !1, Ne(t, "input")) }, Ne = (e, t) => { const n = document.createEvent("HTMLEvents"); n.initEvent(t, !0, !0), e.dispatchEvent(n) }, Te = Object.create(null), Me = (e, t, n) => Be(e, `return(${t})`, n), Be = (e, t, n) => { const s = Te[t] || (Te[t] = Le(t)); try { return s(e, n) } catch (r) { console.error(r) } }, Le = e => { try { return new Function("$data", "$el", `with($data){${e}}`) } catch (t) { return console.error(`${t.message} in expression: ${e}`), () => { } } }, We = { bind: ve, on: Se, show: ({ el: e, get: t, effect: n }) => { const s = e.style.display; n((() => { e.style.display = t() ? s : "none" })) }, text: Ee, html: ({ el: e, get: t, effect: n }) => { n((() => { e.innerHTML = t() })) }, model: ({ el: e, exp: t, get: n, effect: s, modifiers: r }) => { const i = e.type, l = n(`(val) => { ${t} = val }`), { trim: f, number: u = "number" === i } = r || {}; if ("SELECT" === e.tagName) { const t = e; we(e, "change", (() => { const e = Array.prototype.filter.call(t.options, (e => e.selected)).map((e => u ? O(je(e)) : je(e))); l(t.multiple ? e : e[0]) })), s((() => { const e = n(), s = t.multiple; for (let n = 0, r = t.options.length; n < r; n++) { const r = t.options[n], i = je(r); if (s) a(e) ? r.selected = c(e, i) > -1 : r.selected = e.has(i); else if (o(je(r), e)) return void (t.selectedIndex !== n && (t.selectedIndex = n)) } !s && -1 !== t.selectedIndex && (t.selectedIndex = -1) })) } else if ("checkbox" === i) { let t; we(e, "change", (() => { const t = n(), s = e.checked; if (a(t)) { const n = je(e), r = c(t, n), i = -1 !== r; if (s && !i) l(t.concat(n)); else if (!s && i) { const e = [...t]; e.splice(r, 1), l(e) } } else l(Ae(e, s)) })), s((() => { const s = n(); a(s) ? e.checked = c(s, je(e)) > -1 : s !== t && (e.checked = o(s, Ae(e, !0))), t = s })) } else if ("radio" === i) { let t; we(e, "change", (() => { l(je(e)) })), s((() => { const s = n(); s !== t && (e.checked = o(s, je(e))) })) } else { const t = e => f ? e.trim() : u ? O(e) : e; we(e, "compositionstart", Pe), we(e, "compositionend", Re), we(e, (null == r ? void 0 : r.lazy) ? "change" : "input", (() => { e.composing || l(t(e.value)) })), f && we(e, "change", (() => { e.value = e.value.trim() })), s((() => { if (e.composing) return; const s = e.value, r = n(); document.activeElement === e && t(s) === r || s !== r && (e.value = r) })) } }, effect: ({ el: e, ctx: t, exp: n, effect: s }) => { he((() => s((() => Be(t.scope, n, e))))) } }, Ie = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/, Ke = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/, Ve = /^\(|\)$/g, Fe = /^[{[]\s*((?:[\w_$]+\s*,?\s*)+)[\]}]$/, ze = (e, t, n) => { const s = t.match(Ie); if (!s) return; const r = e.nextSibling, i = e.parentElement, o = new Text(""); i.insertBefore(o, e), i.removeChild(e); const c = s[2].trim(); let l, f, u, p, h = s[1].trim().replace(Ve, "").trim(), d = !1, m = "key", v = e.getAttribute(m) || e.getAttribute(m = ":key") || e.getAttribute(m = "v-bind:key"); v && (e.removeAttribute(m), "key" === m && (v = JSON.stringify(v))), (p = h.match(Ke)) && (h = h.replace(Ke, "").trim(), f = p[1].trim(), p[2] && (u = p[2].trim())), (p = h.match(Fe)) && (l = p[1].split(",").map((e => e.trim())), d = "[" === h[0]); let y, b, x, _ = !1; const w = (e, t, s, r) => { const i = {}; l ? l.forEach(((e, n) => i[e] = t[d ? n : e])) : i[h] = t, r ? (f && (i[f] = r), u && (i[u] = s)) : f && (i[f] = s); const o = et(n, i), c = v ? Me(o.scope, v) : s; return e.set(c, s), o.key = c, o }, $ = (t, n) => { const s = new nt(e, t); return s.key = t.key, s.insert(i, n), s }; return n.effect((() => { const e = Me(n.scope, c), t = x; if ([b, x] = (e => { const t = new Map, n = []; if (a(e)) for (let s = 0; s < e.length; s++)n.push(w(t, e[s], s)); else if ("number" == typeof e) for (let s = 0; s < e; s++)n.push(w(t, s + 1, s)); else if (g(e)) { let s = 0; for (const r in e) n.push(w(t, e[r], s++, r)) } return [n, t] })(e), _) { for (let t = 0; t < y.length; t++)x.has(y[t].key) || y[t].remove(); const e = []; let n, s, r = b.length; for (; r--;) { const c = b[r], l = t.get(c.key); let f; null == l ? f = $(c, n ? n.el : o) : (f = y[l], Object.assign(f.ctx.scope, c.scope), l !== r && (y[l + 1] !== n || s === n) && (s = f, f.insert(i, n ? n.el : o))), e.unshift(n = f) } y = e } else y = b.map((e => $(e, o))), _ = !0 })), r }, He = ({ el: e, ctx: { scope: { $refs: t } }, get: n, effect: s }) => { let r; return s((() => { const s = n(); t[s] = e, r && s !== r && delete t[r], r = s })), () => { r && delete t[r] } }, Je = /^(?:v-|:|@)/, Ze = /\.([\w-]+)/g; let qe = !1; const De = (e, t) => { const n = e.nodeType; if (1 === n) { const n = e; if (n.hasAttribute("v-pre")) return; let s; if (_e(n, "v-cloak"), s = _e(n, "v-if")) return ((e, t, n) => { const s = e.parentElement, r = new Comment("v-if"); s.insertBefore(r, e); const i = [{ exp: t, el: e }]; let o, c; for (; (o = e.nextElementSibling) && (c = null, "" === _e(o, "v-else") || (c = _e(o, "v-else-if")));)s.removeChild(o), i.push({ exp: c, el: o }); const l = e.nextSibling; s.removeChild(e); let f, u = -1; const a = () => { f && (s.insertBefore(r, f.el), f.remove(), f = void 0) }; return n.effect((() => { for (let e = 0; e < i.length; e++) { const { exp: t, el: o } = i[e]; if (!t || Me(n.scope, t)) return void (e !== u && (a(), f = new nt(o, n), f.insert(s, r), s.removeChild(r), u = e)) } u = -1, a() })), l })(n, s, t); if (s = _e(n, "v-for")) return ze(n, s, t); if ((s = _e(n, "v-scope")) || "" === s) { const e = s ? Me(t.scope, s) : {}; t = et(t, e), e.$template && Xe(n, e.$template) } const r = null != _e(n, "v-once"); r && (qe = !0), (s = _e(n, "ref")) && Qe(n, He, `"${s}"`, t), Ge(n, t); const i = []; for (const { name: e, value: o } of [...n.attributes]) Je.test(e) && "v-cloak" !== e && ("v-model" === e ? i.unshift([e, o]) : "@" === e[0] || /^v-on\b/.test(e) ? i.push([e, o]) : Ue(n, e, o, t)); for (const [e, o] of i) Ue(n, e, o, t); r && (qe = !1) } else if (3 === n) { const n = e.data; if (n.includes(t.delimiters[0])) { let s, r = [], i = 0; for (; s = t.delimitersRE.exec(n);) { const e = n.slice(i, s.index); e && r.push(JSON.stringify(e)), r.push(`$s(${s[1]})`), i = s.index + s[0].length } i < n.length && r.push(JSON.stringify(n.slice(i))), Qe(e, Ee, r.join("+"), t) } } else 11 === n && Ge(e, t) }, Ge = (e, t) => { let n = e.firstChild; for (; n;)n = De(n, t) || n.nextSibling }, Ue = (e, t, n, s) => { let r, i, o; if (":" === (t = t.replace(Ze, ((e, t) => ((o || (o = {}))[t] = !0, ""))))[0]) r = ve, i = t.slice(1); else if ("@" === t[0]) r = Se, i = t.slice(1); else { const e = t.indexOf(":"), n = e > 0 ? t.slice(2, e) : t.slice(2); r = We[n] || s.dirs[n], i = e > 0 ? t.slice(e + 1) : void 0 } r && (r === ve && "ref" === i && (r = He), Qe(e, r, n, s, i, o), e.removeAttribute(t)) }, Qe = (e, t, n, s, r, i) => { const o = t({ el: e, get: (t = n) => Me(s.scope, t, e), effect: s.effect, ctx: s, exp: n, arg: r, modifiers: i }); o && s.cleanups.push(o) }, Xe = (e, t) => { if ("#" !== t[0]) e.innerHTML = t; else { const n = document.querySelector(t); e.appendChild(n.content.cloneNode(!0)) } }, Ye = e => { const t = { delimiters: ["{{", "}}"], delimitersRE: /\{\{([^]+?)\}\}/g, ...e, scope: e ? e.scope : oe({}), dirs: e ? e.dirs : {}, effects: [], blocks: [], cleanups: [], effect: e => { if (qe) return de(e), e; const n = function (e, t) { e.effect && (e = e.effect.fn); const n = new W(e); t && (l(n, t), t.scope && S(n, t.scope)), (!t || !t.lazy) && n.run(); const s = n.run.bind(n); return s.effect = n, s }(e, { scheduler: () => de(n) }); return t.effects.push(n), n } }; return t }, et = (e, t = {}) => { const n = e.scope, s = Object.create(n); Object.defineProperties(s, Object.getOwnPropertyDescriptors(t)), s.$refs = Object.create(n.$refs); const r = oe(new Proxy(s, { set: (e, t, s, i) => i !== r || e.hasOwnProperty(t) ? Reflect.set(e, t, s, i) : Reflect.set(n, t, s) })); return tt(r), { ...e, scope: r } }, tt = e => { for (const t of Object.keys(e)) "function" == typeof e[t] && (e[t] = e[t].bind(e)) }; class nt { constructor(e, t, n = !1) { C(this, "template"), C(this, "ctx"), C(this, "key"), C(this, "parentCtx"), C(this, "isFragment"), C(this, "start"), C(this, "end"), this.isFragment = e instanceof HTMLTemplateElement, n ? this.template = e : this.isFragment ? this.template = e.content.cloneNode(!0) : this.template = e.cloneNode(!0), n ? this.ctx = t : (this.parentCtx = t, t.blocks.push(this), this.ctx = Ye(t)), De(this.template, this.ctx) } get el() { return this.start || this.template } insert(e, t = null) { if (this.isFragment) if (this.start) { let n, s = this.start; for (; s && (n = s.nextSibling, e.insertBefore(s, t), s !== this.end);)s = n } else this.start = new Text(""), this.end = new Text(""), e.insertBefore(this.end, t), e.insertBefore(this.start, this.end), e.insertBefore(this.template, this.end); else e.insertBefore(this.template, t) } remove() { if (this.parentCtx && ((e, t) => { const n = e.indexOf(t); n > -1 && e.splice(n, 1) })(this.parentCtx.blocks, this), this.start) { const e = this.start.parentNode; let t, n = this.start; for (; n && (t = n.nextSibling, e.removeChild(n), n !== this.end);)n = t } else this.template.parentNode.removeChild(this.template); this.teardown() } teardown() { this.ctx.blocks.forEach((e => { e.teardown() })), this.ctx.effects.forEach(K), this.ctx.cleanups.forEach((e => e())) } } const st = e => e.replace(/[-.*+?^${}()|[\]\/\\]/g, "\\$&"), rt = e => { const t = Ye(); if (e && (t.scope = oe(e), tt(t.scope), e.$delimiters)) { const [n, s] = t.delimiters = e.$delimiters; t.delimitersRE = new RegExp(st(n) + "([^]+?)" + st(s), "g") } let n; return t.scope.$s = Ce, t.scope.$nextTick = he, t.scope.$refs = Object.create(null), { directive(e, n) { return n ? (t.dirs[e] = n, this) : t.dirs[e] }, mount(e) { if ("string" == typeof e && !(e = document.querySelector(e))) return; let s; return s = (e = e || document.documentElement).hasAttribute("v-scope") ? [e] : [...e.querySelectorAll("[v-scope]")].filter((e => !e.matches("[v-scope] [v-scope]"))), s.length || (s = [e]), n = s.map((e => new nt(e, t, !0))), this }, unmount() { n.forEach((e => e.teardown())) } } }, it = document.currentScript; return it && it.hasAttribute("init") && rt().mount(), e.createApp = rt, e.nextTick = he, e.reactive = oe, Object.defineProperty(e, "__esModule", { value: !0 }), e[Symbol.toStringTag] = "Module", e }({});
  </script>

  <script>
    // random

    const random = Math.random;
    const floor = Math.floor;

    /**
     * 随机整数
     * @param {number} n
     * @returns [0, n)
     */
    function intRand(n) {
      return floor(random() * n);
    }

    /**
     * 从范围[a, b)内随机
     * @param {number} a start
     * @param {number} b end
     * @returns [a, b)
     */
    function rand(a, b) {
      if (b) {
        const diff = b - a;
        return a + intRand(diff);
      }
      return intRand(a);
    }

    /**
     * 随机HEX颜色
     */
    function hexColor() {
      return (
        "#" +
        Array(6)
          .fill(0)
          .map(() => rand(16).toString(16))
          .join("")
      );
    }
  </script>

  <script>
    // canvas

    // 创建一个canvas
    function createCanvas(width = 300, height = 150) {
      const canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;
      return canvas;
    }

    /**
     * 生成随机彩色图片URL
     */
    function randImage(
      text,
      width = rand(50, 150),
      height = rand(50, 150),
      bgColor = hexColor(),
      fontColor = "white"
    ) {
      const canvas = createCanvas(width, height);
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = bgColor;
      ctx.fillRect(0, 0, width, height);

      ctx.fillStyle = fontColor;
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.font = Math.min(width, height) / 2 + "px bold";
      ctx.fillText(text, width / 2, height / 2);

      return {
        src: canvas.toDataURL(),
        width,
        height,
      }
    }
  </script>

  <script>
    // utils

    /**
     * 生成一个length大小的数组
     * @param {Number} length
     * @param {Function} func
     * @returns {Array}
     */
    function map(length, func = (i) => i) {
      return Array.from({ length }, (_, i) => func(i));
    }
  </script>

  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, Tahoma, Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    }

    img {
      width: 100%;
      /* border: 4px solid #ccc; */
      display: block;
    }

    .container {
      width: 400px;
      height: 600px;
      /* border: 1px solid red; */
      overflow-y: scroll;
    }

  </style>
</head>

<body>

  <h1>瀑布流布局</h1>

  <p>
    瀑布流(waterfull)布局，也称作masonry。是一种被广泛应用的多图布局，尤其在移动端。对于一个网页，人类是习惯于横向定宽，垂直滚动来进行阅读的。在展示大量图片时，为避免杂乱无章，采用定宽定列的布局方式，可以促使浏览更加高效。
  </p>

  <script>
    const store = PetiteVue.reactive({
      count: 50,
      columnSize: 4,
      images: [],
      makeImages() {
        this.images = map(this.count, (i) => randImage(i + 1))
      },
      makeUglyImages() {
        this.images = map(this.count, (i) => randImage(i + 1,
          i % 2 === 0 ? 50 : undefined,
          i % 2 === 0 ? 150 : undefined))
      }
    })
  </script>

  <script type="module">
    function Controller() {
      return {
        mounted() {
          store.makeImages()
        }
      }
    }
    PetiteVue.createApp({
      Controller
    }).mount("#controller");
  </script>

  <fieldset id="controller" v-scope="Controller()" @vue:mounted="mounted">
    <legend>控制面板</legend>
    图片数量: <input type="number" v-model="store.count" />
    <button @click="store.makeImages()">生成随机图片</button>
    <button @click="store.makeUglyImages()">生成极端图片</button>

    列数:
    <input type="number" v-model="store.columnSize" />

  </fieldset>

  <script type="module">
    function Container_raw() {
      return {}
    }
    PetiteVue.createApp({
      Container_raw
    }).mount(".container__raw");
  </script>

  <fieldset class="container__raw container" v-scope="Container_raw()">
    <legend>图片列表</legend>
    <div class="item" v-for="image in store.images">
      <img :src="image.src" :alt="image.src">
    </div>
  </fieldset>

  <p>太简单，无法在有限的空间内展示更多的信息</p>

  <h2>Column-count</h2>

  <script type="module">
    function Container_Column_count() {
      return {}
    }
    PetiteVue.createApp({
      Container_Column_count
    }).mount(".container__column_count");
  </script>

  <fieldset class="container__column_count container" v-scope="Container_Column_count()"
    :style="`--count: ${store.columnSize}`">
    <legend>Column-count布局</legend>
    <div class="item" v-for="image in store.images">
      <img :src="image.src" :alt="image.src">
    </div>
  </fieldset>

  <style>
    .container__column_count {
      --count: 3;
      column-count: var(--count);
      /* margin-right: 8px; */
      break-inside: avoid;
    }

  </style>

  <p>这样实现的瀑布流先由上至下布局，不符合先从左到右的现代阅读习惯，也不支持<em>滚动加载</em>，并不推荐</p>

  <h2>Flex 基本布局</h2>

  <script type="module">
    const state = PetiteVue.reactive({
      columns: map(store.columnSize, () => [])
    })
    let cur = 0;
    function loadNext(isAuto = true) {
      if (cur >= store.images.length) {
        cur = 0;
        return;
      };
      const index = cur % store.columnSize;
      state.columns[index].push(store.images[cur]);
      cur++;
      isAuto && loadNext();
    }
    loadNext()
    function Container_Flex_basic() {
      return {
        state,
        layout() {
          state.columns = map(store.columnSize, () => []);
          loadNext()
        },
        reset() {
          state.columns = map(store.columnSize, () => []);
        },
        stepByStep() {
          loadNext(false)
        }
      }
    }

    PetiteVue.createApp({
      Container_Flex_basic
    }).mount("#container_scope__flex_basic");
  </script>

  <div id="container_scope__flex_basic" v-scope="Container_Flex_basic()">
    <button @click="layout">布局</button>
    <button @click="reset">重置</button>
    <button @click="stepByStep">手动布局</button>
    <fieldset class="container__flex_basic container">
      <legend>Flex基本布局</legend>
      <div class="column" v-for="column in state.columns">
        <div class="item" v-for="image in column">
          <img :src="image.src" :alt="image.src" />
        </div>
      </div>
    </fieldset>
  </div>

  <style>
    .container__flex_basic .column .item {
      /* margin: 4px 8px; */
      flex: auto;
      width: 100%;
    }

    .container__flex_basic .column {
      display: flex;
      flex-wrap: wrap;
      flex-direction: row;
      align-content: flex-start;
      flex: 1;
    }

    .container__flex_basic {
      display: flex;
    }

  </style>

  <p>极端条件下，视野内不符合图片排名</p>

  <h2>Flex布局的贪心改进</h2>

  <script type="module">
    const state = PetiteVue.reactive({
      columns: map(store.columnSize, () => []),
      heights: map(store.columnSize, () => 0)
    })
    let cur = 0;
    function loadNext(isAuto = true) {
      if (cur >= store.images.length) {
        cur = 0;
        return;
      };
      const minHeightIndex = state.heights.reduce((pi, c, ci, a) => pi = (a[pi] > c) ? ci : pi, 0);
      state.columns[minHeightIndex].push(store.images[cur]);
      const { width, height } = (store.images[cur]);
      state.heights[minHeightIndex] += height / width;

      cur++;
      isAuto && loadNext();
    }
    loadNext();
    function Container__flex_greedy() {
      return {
        state,
        layout() {
          state.columns = map(store.columnSize, () => []);
          state.heights = map(store.columnSize, () => 0);
          loadNext()
        },
        reset() {
          state.columns = map(store.columnSize, () => []);
          state.heights = map(store.columnSize, () => 0);
        },
        stepByStep() {
          loadNext(false)
        }
      }
    }

    PetiteVue.createApp({
      Container__flex_greedy
    }).mount("#container_scope__flex_greedy");
  </script>

  <div id="container_scope__flex_greedy" v-scope="Container__flex_greedy()">
    <button @click="layout">布局</button>
    <button @click="reset">重置</button>
    <button @click="stepByStep">手动布局</button>
    <fieldset class="container__flex_greedy container">
      <legend>Flex改进</legend>
      <div class="column" v-for="column in state.columns">
        <div class="item" v-for="image in column">
          <img :src="image.src" :alt="image.src" />
        </div>
      </div>
    </fieldset>
  </div>

  <style>
    .container__flex_greedy .column .item {
      flex: auto;
      width: 100%;
    }

    .container__flex_greedy .column {
      display: flex;
      flex-wrap: wrap;
      flex-direction: row;
      align-content: flex-start;
      flex: 1;
    }

    .container__flex_greedy {
      display: flex;
    }

  </style>

  <h2>延伸阅读</h2>
  <p>
    <em>
      思考：
    </em>
    给定一组图片,请你分成N列，你能设计一种算法使得最后N列图片的高度差最小吗？
  </p>
  <ul>
    <li>
      <a href="https://juejin.cn/post/6844904178544820237" target="_blank">
        [这个前端竟然用动态规划写瀑布流布局？给我打死他！ - 掘金](https://juejin.cn/post/6844904178544820237)
    </li>
    </a>
  </ul>
</body>

</html>
